package cc.telecomdigital.tdstock.trading.widget;

import java.util.ArrayList;
import java.util.List;

import cc.telecomdigital.tdstock.R;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;

public class WheelView extends View
{
	public interface IOnWheelChangedListener
	{
		void onChanged(WheelView wheel, int oldValue, int newValue);
	}

	public interface IOnWheelClickedListener
	{
		void onItemClicked(WheelView wheel, int itemIndex);
	}

	public interface IOnWheelScrollListener
	{
		void onScrollingStarted(WheelView wheel);

		void onScrollingFinished(WheelView wheel);
	}

	public class ItemsRange
	{
		// First item number
		private int	first;

		// Items count
		private int	count;

		public ItemsRange()
		{
			this(0, 0);
		}

		public ItemsRange(int first, int count)
		{
			this.first = first;
			this.count = count;
		}

		public int getFirst()
		{
			return first;
		}

		public int getLast()
		{
			return getFirst() + getCount() - 1;
		}

		public int getCount()
		{
			return count;
		}

		public boolean contains(int index)
		{
			return index >= getFirst() && index <= getLast();
		}
	}

	/** Top and bottom shadows colors */
	private static final int[]			SHADOWS_COLORS		= new int[] {
			0xFF111111, 0x00AAAAAA, 0x00AAAAAA			};

	/** Top and bottom items offset (to hide that) */
	private static final int				ITEM_OFFSET_PERCENT	= 10;

	/** Left and right padding value */
	private static final int				PADDING			= 10;

	/** Default count of visible items */
	private static final int				DEF_VISIBLE_ITEMS	= 5;

	// Wheel Values
	private int						currentIndex		= 0;

	// Count of visible items
	private int						visibleItems		= DEF_VISIBLE_ITEMS;

	// Item height
	private int						itemHeight		= 0;

	// Center Line
	private Drawable					centerDrawable;

	private GradientDrawable				topShadow;
	private GradientDrawable				bottomShadow;

	// Scrolling
	private WheelScroller				scroller;
	private boolean					isScrollingPerformed;
	private int						scrollingOffset;

	// Items layout
	private LinearLayout				itemsLayout;

	// The number of first item in layout
	private int						firstItem;

	// View adapter
	private WheelViewAdapter				viewAdapter;

	// Recycle
	private WheelRecycle				recycle			= new WheelRecycle(
															this);

	// List Listener or event list listener
	private List<IOnWheelChangedListener>	changingListeners	= new ArrayList<IOnWheelChangedListener>();
	private List<IOnWheelScrollListener>	scrollingListeners	= new ArrayList<IOnWheelScrollListener>();
	private List<IOnWheelClickedListener>	clickingListeners	= new ArrayList<IOnWheelClickedListener>();

	public WheelView(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		initData(context);
	}

	public WheelView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		initData(context);
	}

	public WheelView(Context context)
	{
		super(context);
		initData(context);
	}

	private void initData(Context context)
	{
		scroller = new WheelScroller(getContext(), scrollingListener);
	}

	// Scroller
	WheelScroller.ScrollingListener	scrollingListener	= new WheelScroller.ScrollingListener()
												{
													public void onStarted()
													{
														isScrollingPerformed = true;
														notifyScrollingListenersAboutStart();
													}

													public void onScroll(
															int distance)
													{
														doScroll(distance);

														int height = getHeight();
														if (scrollingOffset > height)
														{
															scrollingOffset = height;
															scroller.stopScrolling();
														}
														else if (scrollingOffset < -height)
														{
															scrollingOffset = -height;
															scroller.stopScrolling();
														}
													}

													public void onFinished()
													{
														if (isScrollingPerformed)
														{
															notifyScrollingListenersAboutEnd();
															isScrollingPerformed = false;
														}

														scrollingOffset = 0;
														invalidate();
													}

													public void onJustify()
													{
														if (Math.abs(scrollingOffset) > WheelScroller.MIN_DELTA_FOR_SCROLLING)
															scroller.scroll(
																	scrollingOffset,
																	0);
													}
												};

	public void setInterpolator(Interpolator interpolator)
	{
		scroller.setInterpolator(interpolator);
	}

	public int getVisibleItems()
	{
		return visibleItems;
	}

	public void setVisibleItems(int count)
	{
		visibleItems = count;
	}

	public WheelViewAdapter getViewAdapter()
	{
		return viewAdapter;
	}

	// Adapter listener
	private DataSetObserver	dataObserver	= new DataSetObserver()
									{
										@Override
										public void onChanged()
										{
											invalidateWheel(false);
										}

										@Override
										public void onInvalidated()
										{
											invalidateWheel(true);
										}
									};

	public void setViewAdapter(WheelViewAdapter viewAdapter)
	{
		if (this.viewAdapter != null)
			this.viewAdapter.unregisterDataSetObserver(dataObserver);

		this.viewAdapter = viewAdapter;
		if (this.viewAdapter != null)
			this.viewAdapter.registerDataSetObserver(dataObserver);
		invalidateWheel(true);
	}

	public void addChangingListener(IOnWheelChangedListener listener)
	{
		changingListeners.add(listener);
	}

	public void removeChangingListener(IOnWheelChangedListener listener)
	{
		changingListeners.remove(listener);
	}

	protected void notifyChangingListeners(int oldValue, int newValue)
	{
		for (IOnWheelChangedListener listener : changingListeners)
			listener.onChanged(this, oldValue, newValue);
	}

	public void addScrollingListener(IOnWheelScrollListener listener)
	{
		scrollingListeners.add(listener);
	}

	public void removeScrollingListener(IOnWheelScrollListener listener)
	{
		scrollingListeners.remove(listener);
	}

	protected void notifyScrollingListenersAboutStart()
	{
		for (IOnWheelScrollListener listener : scrollingListeners)
			listener.onScrollingStarted(this);
	}

	protected void notifyScrollingListenersAboutEnd()
	{
		for (IOnWheelScrollListener listener : scrollingListeners)
			listener.onScrollingFinished(this);
	}

	public void addClickingListener(IOnWheelClickedListener listener)
	{
		clickingListeners.add(listener);
	}

	public void removeClickingListener(IOnWheelClickedListener listener)
	{
		clickingListeners.remove(listener);
	}

	protected void notifyClickListenersAboutClick(int item)
	{
		for (IOnWheelClickedListener listener : clickingListeners)
		{
			listener.onItemClicked(this, item);
		}
	}

	public int getCurrentItemIndex()
	{
		return currentIndex;
	}

	public void setCurrentItem(int index, boolean animated)
	{
		if (viewAdapter == null || viewAdapter.getItemsCount() == 0)
			return;

		int itemCount = viewAdapter.getItemsCount();
		if (index < 0 || index >= itemCount)
			return;

		if (index != currentIndex)
		{
			if (animated)
				scroll(index - currentIndex, 0);
			else
			{
				scrollingOffset = 0;

				int old = currentIndex;

				currentIndex = index;
				notifyChangingListeners(old, currentIndex);
				invalidate();
			}
		}
	}

	public void setCurrentItem(int index)
	{
		setCurrentItem(index, false);
	}

	public void invalidateWheel(boolean clearCaches)
	{
		if (clearCaches)
		{
			recycle.clearAll();
			if (itemsLayout != null)
				itemsLayout.removeAllViews();
			scrollingOffset = 0;
		}
		else if (itemsLayout != null)
		{
			// cache all items
			recycle.recycleItems(itemsLayout, firstItem, new ItemsRange());
		}
		invalidate();
	}

	private void initResourcesIfNecessary()
	{
		if (centerDrawable == null)
			centerDrawable = getContext().getResources().getDrawable(
					R.drawable.wheel_val);

		if (topShadow == null)
			topShadow = new GradientDrawable(Orientation.TOP_BOTTOM,
					SHADOWS_COLORS);

		if (bottomShadow == null)
			bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP,
					SHADOWS_COLORS);

		setBackgroundResource(R.drawable.wheel_bg);
	}

	private int getDesiredHeight(LinearLayout layout)
	{
		if (layout != null && layout.getChildAt(0) != null)
			itemHeight = layout.getChildAt(0).getMeasuredHeight();

		int desired = itemHeight * visibleItems - itemHeight
				* ITEM_OFFSET_PERCENT / 50;

		return Math.max(desired, getSuggestedMinimumHeight());
	}

	private int getItemHeight()
	{
		if (itemHeight != 0)
			return itemHeight;

		if (itemsLayout != null && itemsLayout.getChildAt(0) != null)
		{
			itemHeight = itemsLayout.getChildAt(0).getHeight();
			return itemHeight;
		}
		return getHeight() / visibleItems;
	}

	private int calculateLayoutWidth(int widthSize, int mode)
	{
		initResourcesIfNecessary();

		itemsLayout.setLayoutParams(new LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		itemsLayout.measure(MeasureSpec.makeMeasureSpec(widthSize,
				MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0,
				MeasureSpec.UNSPECIFIED));

		int width = itemsLayout.getMeasuredWidth();

		if (mode == MeasureSpec.EXACTLY)
			width = widthSize;
		else
		{
			width += 2 * PADDING;

			// Check against our minimum width
			width = Math.max(width, getSuggestedMinimumWidth());

			if (mode == MeasureSpec.AT_MOST && widthSize < width)
				width = widthSize;
		}

		itemsLayout.measure(MeasureSpec.makeMeasureSpec(width - 2 * PADDING,
				MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0,
				MeasureSpec.UNSPECIFIED));
		return width;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		buildViewForMeasuring();

		int width = calculateLayoutWidth(widthSize, widthMode);

		int height;
		if (heightMode == MeasureSpec.EXACTLY)
			height = heightSize;
		else
		{
			height = getDesiredHeight(itemsLayout);

			if (heightMode == MeasureSpec.AT_MOST)
				height = Math.min(height, heightSize);
		}
		setMeasuredDimension(width, height);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b)
	{
		layout(r - l, b - t);
	}

	private void layout(int width, int height)
	{
		int itemsWidth = width - 2 * PADDING;

		itemsLayout.layout(0, 0, itemsWidth, height);
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if (viewAdapter != null && viewAdapter.getItemsCount() > 0)
		{
			updateView();

			drawItems(canvas);
			drawCenterRect(canvas);
		}
		drawShadows(canvas);
	}

	private void drawShadows(Canvas canvas)
	{
		int height = (int) (1.5 * getItemHeight());

		topShadow.setBounds(0, 0, getWidth(), height);
		topShadow.draw(canvas);

		bottomShadow.setBounds(0, getHeight() - height, getWidth(),
				getHeight());
		bottomShadow.draw(canvas);
	}

	private void drawItems(Canvas canvas)
	{
		canvas.save();

		int top = (currentIndex - firstItem) * getItemHeight()
				+ (getItemHeight() - getHeight()) / 2;
		canvas.translate(PADDING, -top + scrollingOffset);

		itemsLayout.draw(canvas);
		canvas.restore();
	}

	private void drawCenterRect(Canvas canvas)
	{
		int center = getHeight() / 2;
		int offset = (int) (getItemHeight() / 2 * 1.2);
		centerDrawable.setBounds(0, center - offset, getWidth(), center
				+ offset);
		centerDrawable.draw(canvas);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		if (!isEnabled() || getViewAdapter() == null)
			return true;

		if (getParent() != null)
			getParent().requestDisallowInterceptTouchEvent(true);
		
		switch (event.getAction())
		{
//			case MotionEvent.ACTION_MOVE:
//				if (getParent() != null)
//					getParent().requestDisallowInterceptTouchEvent(true);
//				break;

			case MotionEvent.ACTION_UP:
				if (!isScrollingPerformed)
				{
					int distance = (int) event.getY() - getHeight() / 2;
					if (distance > 0)
						distance += getItemHeight() / 2;
					else
						distance -= getItemHeight() / 2;

					int items = distance / getItemHeight();
					if (items != 0
							&& isValidItemIndex(currentIndex + items))
						notifyClickListenersAboutClick(currentIndex
								+ items);
				}
				break;
		}
		return scroller.onTouchEvent(event);
	}

	private void doScroll(int delta)
	{
		scrollingOffset += delta;

		int itemHeight = getItemHeight();
		int count = scrollingOffset / itemHeight;

		int pos = currentIndex - count;
		int itemCount = viewAdapter.getItemsCount();

		int fixPos = scrollingOffset % itemHeight;
		if (Math.abs(fixPos) <= itemHeight / 2)
			fixPos = 0;

		if (pos < 0)
		{
			count = currentIndex;
			pos = 0;
		}
		else if (pos >= itemCount)
		{
			count = currentIndex - itemCount + 1;
			pos = itemCount - 1;
		}
		else if (pos > 0 && fixPos > 0)
		{
			pos--;
			count++;
		}
		else if (pos < itemCount - 1 && fixPos < 0)
		{
			pos++;
			count--;
		}

		int offset = scrollingOffset;

		if (pos != currentIndex)
			setCurrentItem(pos, false);
		else
			invalidate();

		// update offset
		scrollingOffset = offset - count * itemHeight;
		if (scrollingOffset > getHeight())
			scrollingOffset = scrollingOffset % getHeight() + getHeight();
	}

	public void scroll(int itemsToScroll, int time)
	{
		int distance;

		distance = itemsToScroll * getItemHeight() - scrollingOffset;
		scroller.scroll(distance, time);
	}

	private ItemsRange getItemsRange()
	{
		if (getItemHeight() == 0)
			return null;

		int first = currentIndex;
		int count = 1;

		while (count * getItemHeight() < getHeight())
		{
			first--;
			count += 2; // top + bottom items
		}

		if (scrollingOffset != 0)
		{
			if (scrollingOffset > 0)
				first--;
			count++;

			// process empty items above the first or below the second
			int emptyItems = scrollingOffset / getItemHeight();

			first -= emptyItems;
			count += Math.asin(emptyItems);
		}
		return new ItemsRange(first, count);
	}

	private boolean rebuildItems()
	{
		boolean updated = false;
		ItemsRange range = getItemsRange();
		if (itemsLayout != null)
		{
			int first = recycle.recycleItems(itemsLayout, firstItem, range);
			updated = firstItem != first;
			firstItem = first;
		}
		else
		{
			createItemsLayout();
			updated = true;
		}

		if (!updated)
		{
			updated = firstItem != range.getFirst()
					|| itemsLayout.getChildCount() != range.getCount();
		}

		if (firstItem > range.getFirst() && firstItem <= range.getLast())
		{
			for (int i = firstItem - 1; i >= range.getFirst(); i--)
			{
				if (!addViewItem(i, true))
					break;
				firstItem = i;
			}
		}
		else
			firstItem = range.getFirst();

		int first = firstItem;
		for (int i = itemsLayout.getChildCount(); i < range.getCount(); i++)
		{
			if (!addViewItem(firstItem + i, false)
					&& itemsLayout.getChildCount() == 0)
				first++;
		}
		firstItem = first;
		return updated;
	}

	private void updateView()
	{
		if (rebuildItems() == false)
			return;
		calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
		layout(getWidth(), getHeight());
	}

	private void createItemsLayout()
	{
		if (itemsLayout == null)
		{
			itemsLayout = new LinearLayout(getContext());
			itemsLayout.setOrientation(LinearLayout.VERTICAL);
		}
	}

	private void buildViewForMeasuring()
	{
		// clear all items
		if (itemsLayout != null)
			recycle.recycleItems(itemsLayout, firstItem, new ItemsRange());
		else
			createItemsLayout();

		// add views
		int addItems = visibleItems / 2;
		for (int i = currentIndex + addItems; i >= currentIndex - addItems; i--)
		{
			if (addViewItem(i, true))
				firstItem = i;
		}
	}

	private boolean addViewItem(int index, boolean first)
	{
		View view = getItemView(index);
		if (view == null)
			return false;

		if (first)
			itemsLayout.addView(view, 0);
		else
			itemsLayout.addView(view);

		return true;
	}

	private boolean isValidItemIndex(int index)
	{
		return (viewAdapter != null) && (viewAdapter.getItemsCount() > 0)
				&& ((index >= 0) && (index < viewAdapter.getItemsCount()));
	}

	private View getItemView(int index)
	{
		if (viewAdapter == null || viewAdapter.getItemsCount() == 0)
			return null;

		int count = viewAdapter.getItemsCount();
		if (!isValidItemIndex(index))
			return viewAdapter.getEmptyItem(recycle.getEmptyItem(),
					itemsLayout);

		while (index < 0)
			index = count + index;

		index %= count;
		return viewAdapter.getItem(index, recycle.getItem(), itemsLayout);
	}

	public void stopScrolling()
	{
		scroller.stopScrolling();
	}

	// Wheel Recycle
	public class WheelRecycle
	{
		// Cached items
		private List<View>	items;

		// Cached empty items
		private List<View>	emptyItems;

		// Wheel view
		private WheelView	wheel;

		public WheelRecycle(WheelView wheel)
		{
			this.wheel = wheel;
		}

		public int recycleItems(LinearLayout iLayout, int iFirstIem,
				WheelView.ItemsRange range)
		{
			int index = iFirstIem;

			for (int i = 0; i < iLayout.getChildCount();)
			{
				if (!range.contains(index))
				{
					recycleView(iLayout.getChildAt(i), index);
					iLayout.removeViewAt(i);
					if (i == 0)
						iFirstIem++;
				}
				else
					++i;
				index++;
			}
			return iFirstIem;
		}

		public View getItem()
		{
			return getCachedView(items);
		}

		public View getEmptyItem()
		{
			return getCachedView(emptyItems);
		}

		public void clearAll()
		{
			if (items != null)
				items.clear();
			if (emptyItems != null)
				emptyItems.clear();
		}

		private List<View> addView(View iViewObj, List<View> iCache)
		{
			if (iCache == null)
				iCache = new ArrayList<View>();

			iCache.add(iViewObj);
			return iCache;
		}

		private void recycleView(View iViewObj, int index)
		{
			int count = wheel.getViewAdapter().getItemsCount();

			if ((index < 0 || index >= count))
				emptyItems = addView(iViewObj, emptyItems);
			else
			{
				while (index < 0)
					index = count + index;

				index %= count;
				items = addView(iViewObj, items);
			}
		}

		private View getCachedView(List<View> iCache)
		{
			if (iCache != null && iCache.size() > 0)
			{
				View iViewObj = iCache.get(0);

				iCache.remove(0);
				return iViewObj;
			}
			return null;
		}
	}
}
